package com.jbm.core.common;

import com.jbm.core.client.MainCallback;
import com.jbm.core.common.ResponseRecvCallback.IP2P_RRCallback;
import com.jbm.core.common.ResponseRecvCallback.KeepliveRRCallback;
import com.jbm.core.common.ResponseRecvCallback.ResponseRecvCallback;
import com.jbm.core.iface.IAcross;
import com.jbm.core.iface.IConnectAgent;
import com.jbm.core.iface.IP2P;
import com.jbm.core.pb.PBIAcross;
import com.jbm.core.server.AccountCallback;
import com.jbm.core.server.SAccount;

import org.apache.mina.core.filterchain.IoFilterAdapter;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

import java.net.InetSocketAddress;
import java.util.HashMap;

/**
 * Created by root on 17-3-3.
 */
public class ConnectAgent extends IConnectAgent implements Life
{
	public final static char Send_Type_Proxy = 1;
	public final static char Send_Type_Any = 2;
	public final static char Send_Type_Direct = 3;

	private IAccount m_iaccount;

	protected int m_type;

	protected ConnectAgent m_proxy;

	protected int m_qindex_recv;
	protected final Object m_qindex_r_mutex = new Object();
	protected int m_qindex_send;
	protected final Object m_qindex_s_mutex = new Object();

	protected final Object m_connect_mutex = new Object();
	protected final Object m_life_status_mutex = new Object();

	protected boolean m_isdirect;

	protected boolean m_connectisclose;

	protected int m_sessiontimeout;

	private boolean m_reconnect;

	private NioSocketConnector m_connector;
	private ConnectFuture m_cf;
	private IoSession ios;

	public ConnectAgent()
	{
		super();

		m_iaccount = null;

		m_type = -1;
		m_proxy = null;
		m_qindex_recv = -1;
		m_qindex_send = 0;

		m_isdirect = false;
		m_connectisclose = false;

		m_life_status = life_status_newlife;
		m_isasked = false;
		m_resend = true;

		m_iosession = null;

		m_excodemap = new HashMap<Integer, IAcross>();

		m_reconnect = true;
	}

	public ConnectAgent(IConnectAgent ia, int type, ConnectAgent proxy, int localport)
	{
		super(ia);

		m_iaccount = null;

		m_type = type;
		m_proxy = proxy;

		m_localport = localport;

		m_qindex_recv = -1;
		m_qindex_send = 0;

		m_isdirect = false;
		m_connectisclose = false;

		m_life_status = life_status_newlife;
		m_isasked = false;
		m_resend = true;

		m_iosession = null;

		m_reconnect = true;
	}

	public ConnectAgent(String app_type, String remotehost, int remoteport, int localport, int type)
	{
		this();

		String[] at = app_type.split(":");

		if ( at.length > 1 )
			m_device_id = Integer.getInteger(at[1]);
		else
			m_device_id = 0;

		m_app_type = app_type;

		m_remotehost = remotehost;
		m_remoteport = remoteport;
		m_localport = localport;

		m_type = type;
	}

	public ConnectAgent(String app_type, String remotehost, int remoteport, int localport, int type, int timeout)
	{
		this(app_type,remotehost,remoteport,localport,type);

		m_sessiontimeout = timeout;
	}

	public boolean Match_QIndex(int qidnex)
	{
		return true;
/*
		synchronized (m_qindex_r_mutex)
		{
			if ( m_qindex_recv >= qidnex )
				return false;

			m_qindex_recv = qidnex;
		}

		return true;
*/
	}

	public Integer GetQIndex()
	{
		synchronized (m_qindex_s_mutex)
		{
			return m_qindex_send++;
		}
	}

	public boolean ConnectServer( )
	{
		synchronized (m_connect_mutex)
		{
			m_reconnect = true;

			if ( m_connectisclose )
				return false;

			if (m_app_type.equals("app_server"))
			{
				if (ios == null || !ios.isConnected())
				{
					m_isdirect = false;

					m_connector = new NioSocketConnector();
					m_connector.setConnectTimeoutMillis(m_sessiontimeout);
					m_connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new OHCodeFactory("UTF-8")));
					m_connector.setDefaultRemoteAddress(new InetSocketAddress(m_remotehost, m_remoteport));
					SocketSessionConfig cfg = m_connector.getSessionConfig();
					cfg.setUseReadOperation(true);

					m_connector.setHandler(new MainHandler());

					m_connector.getFilterChain().addFirst("reconnection", new IoFilterAdapter()
					{
						@Override
						public void sessionClosed(NextFilter nextFilter, IoSession iosession) throws Exception
						{
							for (; m_reconnect; )
							{
								try
								{
									m_cf = m_connector.connect();
									m_cf.awaitUninterruptibly();// 等待连接创建成功
									ios = m_cf.getSession();// 获取会话
									if (ios.isConnected())
									{
										ios.setAttribute("CA_Key",ConnectAgent.this);

										ReConnectP2P(new IoSessionShell(ios,null,MainServer.Server_type_Client));

										if ( MainServer.getG_server().islogin() )
											MainServer.getG_server().login();

										break;
									}
								} catch (Exception ex)
								{
								}
								Thread.sleep(3000);
							}
						}
					});

					m_cf = m_connector.connect ( );
					m_cf.awaitUninterruptibly ( );

					ios = m_cf.getSession();

					ios.setAttribute("CA_Key",this);

					return ReConnectP2P(new IoSessionShell(ios,null,MainServer.Server_type_Client));
				}

				m_isdirect = true;

				return true;
			}
		}

		return false;
	}

	public void DisconnectServer( boolean reconnect )
	{
		if ( ios != null && ios.isConnected() )
		{
			m_reconnect = reconnect;

			ios.close(false);
			ios = null;
		}
	}

	public boolean ReConnectP2P(IoSessionShell ios)
	{
		IAcross ars = AutoCall.g_autocall.Call(ios,
				"1st", "",
				null,
				0, m_iaccount.getM_account_id(),
				MainServer.getG_server().getM_app_type(), m_app_type,
				GetQIndex(), true, true, false, false, null,5,6000);

		if (ars.m_code <= 200)
		{
			if ( m_lifename == null )
			{
				Born(0, 15000);
				setM_keepalive(true);
				setM_iosession(ios.getM_iosession());

				AccountCtrl.g_accountctrl.AddCA(this);
			}

			setM_iosession(ios.getM_iosession());

			m_isdirect = true;

			return true;
		} else
			ios.Sclose(false);

		m_isdirect = false;

		return false;
	}

	public boolean ReConnectP2P(boolean init,boolean initiate, IoSessionShell ios)
	{
		if ( init )
		{
			m_connectisclose = true;

			if ( initiate )
			{
				IoSessionShell iosession = null;
				Object res = null;

				if ( m_proxy != null )
					iosession = m_proxy.getM_iosessionShell(MainServer.Server_type_Client);

				if ( iosession != null )
				{
					res = AutoCall.g_autocall.Call(iosession,
							"IP2P", "ConnectP2P",
							new IP2P(MainServer.getG_server().getM_local_id(), m_iaccount.getM_account_id(), m_app_type, initiate),
							MainServer.getG_server().getM_local_id(), m_iaccount.getM_account_id(),
							MainServer.getG_server().getM_app_type(), m_app_type,
							GetQIndex(), true, false, false, false,
							new IP2P_RRCallback(this, ios),5,6000);

					if ( res != null )
						m_connectisclose = false;
				}else
					m_connectisclose = false;
			}else
			{
				Object res = AutoCall.g_autocall.Call(ios,
						"IP2P", "ArcossNAT",
						new IP2P(MainServer.getG_server().getM_local_id(), m_iaccount.getM_account_id(), m_app_type, false),
						MainServer.getG_server().getM_local_id(), m_iaccount.getM_account_id(),
						MainServer.getG_server().getM_app_type(), m_app_type,
						GetQIndex(), true, false, false, false,
						new IP2P_RRCallback(this, ios),5,6000);

				if ( res != null )
					m_connectisclose = false;
			}
		}else
		{
			if ( m_lifename == null )
			{
				Born(0, 15000);
				setM_keepalive(initiate);
				setM_iosession(ios.getM_iosession());

				AccountCtrl.g_accountctrl.AddCA(this);
			}

			setM_iosession(ios.getM_iosession());

			m_isdirect = true;
			m_connectisclose = false;

			System.out.println("NAT..................................................Arcoss");

			return true;
		}

		return true;
	}

	public boolean ConnectP2P(boolean initiate)
	{
		synchronized (m_connect_mutex)
		{
			if ( m_connectisclose )
				return false;

			IoSessionShell ios = null;

			if (m_app_type.equals("app_server"))
			{
				ios = getM_iosessionShell(MainServer.Server_type_Client);

				if (ios == null || !ios.SisConnected())
				{
					m_isdirect = false;

					ios = new IoSessionShell(P2PServer.g_p2pserver.getM_p2pacceptor().newSession(
							new InetSocketAddress(m_remotehost, m_remoteport),
							new InetSocketAddress(m_localport)),null,MainServer.Server_type_Client);

					return ReConnectP2P(ios);
				}

				m_isdirect = true;

				return true;
			} else
			{
				if (!m_proxy.ConnectP2P(true))
					return false;

				ios = getM_iosessionShell(MainServer.Server_type_Client);

				if (ios == null || !ios.SisConnected())
				{
					m_isdirect = false;

					ios = new IoSessionShell(P2PServer.g_p2pserver.getM_p2pacceptor().newSession(
							new InetSocketAddress(m_remotehost, m_remoteport),
							new InetSocketAddress(m_localport)),null,MainServer.Server_type_Client);

					return ReConnectP2P(true,initiate, ios);
				}

				return true;
			}
		}
	}

	public boolean Send ( PBIAcross.IAcross pbrequest, char send_type, char server_type )
	{
		IoSessionShell iosession = null;

		if ( MainServer.getG_server().getM_server_type() == MainServer.Server_type_Client )
		{
			if ( send_type == Send_Type_Direct )
				iosession = getM_iosessionShell(server_type);
			else if ( send_type == Send_Type_Any )
			{
				if ( m_isdirect )
					iosession = getM_iosessionShell(server_type);

				if ( iosession == null && m_proxy != null )
					iosession = m_proxy.getM_iosessionShell(server_type);
			}else if ( send_type == Send_Type_Proxy )
				if (m_proxy != null)
					iosession = m_proxy.getM_iosessionShell(server_type);
		}else
			iosession = getM_iosessionShell(server_type);

		if ( iosession != null )
		{
			WriteFuture wf = iosession.Swrite(pbrequest);
			try
			{
				wf.await();
			}catch (Exception e)
			{
			}

/*
			IoBuffer iobuf = IoBuffer.allocate(100).setAutoExpand(true);
			iobuf.put(pbrequest.toByteArray());
			iobuf.flip();

			iosession.write(iobuf);
*/

			return true;
		}

		return false;
	}

	public IAcross Send (Integer to_id, String app_type,
	                     String classname, String method, IAcross content,
	                     boolean hasresp, boolean waitresp, boolean excode, boolean intask, char send_type,
	                     ResponseRecvCallback rrcallback, char server_type,
	                     int trytimes, int timeout)
	{
		IoSessionShell ios = null;

		Integer from_id = MainServer.getG_server().getM_local_id();
		String from_tag = MainServer.getG_server().getM_app_type();

		if ( MainServer.getG_server().getM_server_type() == MainServer.Server_type_Client )
		{
			if ( to_id == null )
				to_id = m_iaccount.getM_account_id();

			if ( app_type == null )
				app_type = m_app_type;

			ios = getM_iosessionShell(server_type);

			if ( !m_isdirect )
				ios = null;

			if ( send_type == Send_Type_Direct )
				ios = getM_iosessionShell(server_type);
			else if ( send_type == Send_Type_Any )
			{
				if ( ios == null && m_proxy != null )
					ios = m_proxy.getM_iosessionShell(server_type);
			}else if ( send_type == Send_Type_Proxy )
				if (m_proxy != null)
					ios = m_proxy.getM_iosessionShell(server_type);
				else
					ios = null;

			if ( ios == null )
			{
				MainCallback callback = MainServer.getG_server().getM_maincallback();

				if ( callback != null )
				{
					callback.ReConnect();
				}

				return new IAcross(400, "Not Connect");
			}
		}else
		{
			ios = getM_iosessionShell(server_type);

			if ( server_type == MainServer.Server_type_Client )
			{
				if ( m_iaccount != null )
					from_id = m_iaccount.getM_account_id();
				else
					from_id = null;

				from_tag = m_app_type;
			}else if ( to_id == null && m_iaccount != null )
					to_id = m_iaccount.getM_account_id();
		}

		if ( classname == null )
			classname = content.getClass().getSimpleName();

		int qindex = 0;
		if ( intask && MainServer.getG_server().getM_server_type() == MainServer.Server_type_Client )
			qindex = AccountCtrl.g_accountctrl.GetCA(AccountCtrl.Account_Server_id,"app_server").GetQIndex();
		else
			qindex = GetQIndex();

		return AutoCall.g_autocall.Call(ios,
				classname,method,
				content,
				from_id,to_id,
				from_tag,app_type,qindex,
				hasresp,waitresp,excode,intask,rrcallback,
				trytimes,timeout);
	}

	public void Disconnect4Server()
	{
		if ( m_iaccount != null )
			m_iaccount.Offline2Map(this,new AccountCallback((SAccount) m_iaccount));
	}

	public IAccount getM_iaccount()
	{
		return m_iaccount;
	}

	public void setM_iaccount(IAccount ia)
	{
		m_iaccount = ia;
	}

	public int getM_type()
	{
		return m_type;
	}

	public void setM_connectisclose(boolean isclose )
	{
		m_connectisclose = isclose;
	}

	protected int m_retry;
	protected int m_timeout;
	private int m_life_status;
	protected Resource m_resource;
	protected ResKey m_lifename;
	protected Object[] m_param;
	protected long m_borntimems;
	protected long m_lasttryms;
	protected int m_trytimes;

	protected IoSession m_iosession;

	protected boolean m_isasked;
	protected boolean m_resend;
	protected boolean m_keepalive;

	private HashMap<Integer,IAcross> m_excodemap;

	public ResKey getM_lifename()
	{
		return m_lifename;
	}

	public void setM_iosession (IoSession iosession)
	{
		m_iosession = iosession;
	}

	public IoSession getM_iosession()
	{
		return m_iosession;
	}

	public IoSessionShell getM_iosessionShell( char server_type )
	{
		return new IoSessionShell( m_iosession, this, server_type );
	}

	public void setM_keepalive(boolean keepalive)
	{
		m_keepalive = keepalive;
	}

	public HashMap<Integer,IAcross> getM_excodemap()
	{
		return m_excodemap;
	}

	public void unauthorize( boolean offline )
	{
		synchronized (m_ca_status_mutex)
		{
			m_ca_status = ca_status_unauthorized;

			if ( m_iaccount != null )
				m_iaccount.Offline2Map(this,new AccountCallback((SAccount) m_iaccount));

			Send(null, null, null, "belogout", new IConnectAgent(" "), true, false, false, false, ConnectAgent.Send_Type_Any, null, MainServer.Server_type_Server, 0, 5000);

			if ( offline )
			{
				ReleaseFromPool();
				getM_iosessionShell(MainServer.Server_type_Server).Sclose(false);
			}
		}
	}

	@Override
	public void Born(int retry, int timeout)
	{
		m_borntimems = System.currentTimeMillis();
		m_lasttryms = m_borntimems;
		m_timeout = timeout;

		m_retry = retry;
		m_trytimes = 0;
	}

	@Override
	public boolean AmILiving()
	{
		return true;
	}

	@Override
	public boolean IamLiving()
	{
		if ( m_type == MainServer.Server_type_Server)
		{
			m_lasttryms = System.currentTimeMillis();

			if ( m_iaccount instanceof Life)
				((Life)m_iaccount).IamLiving();
		}
		return true;
	}

	@Override
	public boolean RuLiving()
	{
		return true;
	}

	@Override
	public boolean RuLivingWell()
	{
		return true;
	}

	@Override
	public boolean Wakeup(Object... args)
	{
		long cms = System.currentTimeMillis();

		if ( m_lasttryms + m_timeout < cms )
		{
			if ( m_type == MainServer.Server_type_Client)
			{
				m_lasttryms = cms;

				if (m_keepalive)
				{
					m_trytimes ++;
					Send(null, null, "keepalive", "", null, true, false, false, false, ConnectAgent.Send_Type_Any, new KeepliveRRCallback(this,getM_iosessionShell(MainServer.Server_type_Client)),MainServer.Server_type_Client,0,5000);
				}
			}else
				Die();
		}

		return true;
	}

	@Override
	public int IsLast ( Object... args )
	{
		return 0;
	}

	@Override
	public boolean Die()// server三种结束方式 1:主动切断由IoSession结束引发的. 2:Session超时引发的 3:同种客户端同时登录前一个被顶掉的(method Break)
	{
		synchronized (m_ca_status_mutex)
		{
			m_ca_status = ca_status_unauthorized;

			if ( m_iaccount != null )
				m_iaccount.Offline2Map(this,new AccountCallback((SAccount) m_iaccount));

			ReleaseFromPool();
			getM_iosessionShell(MainServer.Server_type_Server).Sclose(false);
		}

		return true;
	}

	@Override
	public boolean BindParameter(Object... param)
	{
		m_param = param;
		return true;
	}

	@Override
	public Object GetParameter(int index)
	{
		return m_param[index];
	}

	@Override
	public boolean BindLifeName(ResKey key)
	{
		m_lifename = key;
		return true;
	}

	@Override
	public ResKey GetLifeName()
	{
		return m_lifename;
	}

	@Override
	public boolean SetStatus(int status)
	{
		synchronized ( m_life_status_mutex )
		{
			m_life_status = status;
			return true;
		}
	}

	@Override
	public int GetStatus()
	{
		return m_life_status;
	}

	@Override
	public int GetOverTime()
	{
		return m_timeout;
	}

	@Override
	public boolean ReleaseFromPool ( )
	{
		if ( m_resource != null )
			return m_resource.Drop(m_lifename);
		else
			return false;
	}

	@Override
	public boolean BindPool ( Resource resource )
	{
		m_resource = resource;
		return true;
	}

	@Override
	public Object[] GetUniqueIndex ()
	{
		Object[] rtn = new Object[1];
		rtn[0] = m_remotehost+":"+m_remoteport;
		return rtn;
	}

	@Override
	public void SetUniqueIndex ( Object... uniqueidx )
	{
	}
}
